package com.smsc.headend.task.engine.executor.impl.firmware;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.HexUtil;
import com.smsc.headend.common.enums.errorcode.TaskExecuteErrorCode;
import com.smsc.headend.common.utils.RedisKeys;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.asset.dmo.ComConfigDMO;
import com.smsc.headend.module.asset.dmo.MeterConfigDMO;
import com.smsc.headend.module.asset.entity.ComEntity;
import com.smsc.headend.module.asset.entity.Meter;
import com.smsc.headend.module.common.entity.DeviceOperation;
import com.smsc.headend.module.data.dmo.DeviceSAPandKeys;
import com.smsc.headend.module.data.dto.AnsiSubDataItemDTO;
import com.smsc.headend.module.data.dto.DataItemDTO;
import com.smsc.headend.module.data.dto.TaskDataDTO;
import com.smsc.headend.module.data.enums.DictionaryType;
import com.smsc.headend.module.fwu.entity.TemplateFwUpgrade;
import com.smsc.headend.module.protocol.ProtocolType;
import com.smsc.headend.module.protocol.cosem.util.ByteUtils;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskExecutorException;
import com.smsc.headend.task.engine.executor.TaskExecutor;
import com.smsc.headend.task.engine.facade.DataItemFacade;
import com.smsc.headend.task.engine.handler.impl.firmware.ImageBlockTransferHandler;
import com.smsc.headend.module.task.dto.AtomicTaskState;
import com.smsc.headend.task.engine.service.ConnectorService;
import com.smsc.headend.task.engine.service.FeignAssetManagementService;
import com.smsc.headend.task.engine.service.asset.AssetService;
import com.smsc.headend.task.engine.service.firmware.FirmwareService;
import com.smsc.headend.task.engine.service.task.TaskPacketService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author liangli
 * @date 2020/9/24
 */
@Slf4j
public class ImageBlockTransferExecutor implements TaskExecutor {
    @Autowired
    TaskPacketService packetService;
    @Autowired
    ConnectorService connectorService;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    FeignAssetManagementService assetManagementRemoteService;
    @Autowired
    DataItemFacade dataItemFacade;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    AssetService assetService;
    @Autowired
    FirmwareService firmwareService;

    @Override
    public AtomicTaskState execute(Task task, AtomicTask atomicTask) throws UDISTaskExecutorException {
        Map<String, Object> paramMap = this.getParameterMap(task, atomicTask);
        if (!paramMap.containsKey("fwFileId")) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.FW_FILE_NOT_SPECIFIED.toString());
        }
        Long fwFileId = Convert.toLong(paramMap.get("fwFileId"));
        Integer blockSize;
        ComConfigDMO comConfigDMO = assetService.getComConfigById(task.getComId());
        if(comConfigDMO.getProtocolId() == ProtocolType.ANSI_C12_18_Protocol.getProtocolId()) {
            blockSize = Convert.toInt(assetService.getSystemProperty("MAX_BLOCK_SIZE"));
        } else {
            List<TaskDataDTO> dataDTOS = taskDAO.getAllTaskData(task.getTaskNo());
            TaskDataDTO dataDTO = dataDTOS.stream().filter(taskDataDTO -> taskDataDTO.getKey().equalsIgnoreCase("blockSize")).findAny().get();
            blockSize = Integer.valueOf(String.valueOf(dataDTO.getValue()));
        }
        AtomicTaskState state = taskDAO.hashGetAtomicTaskState(atomicTask.getAtomicTaskNo());
        Integer blockNumber = state != null && state.getBlockNumber() != null ? state.getBlockNumber().intValue() : 0;
        DeviceOperation dictionary = assetService.getDictionary(atomicTask.getOperationId(), DictionaryType.Operation.getType());
        Meter meter = getMeter(task, assetService);
        if (task.getMeterId() != null && meter == null) {
            throw new UDISTaskExecutorException(TaskExecuteErrorCode.METER_NOT_EXISTS.toString());
        }
        ComEntity terminal = assetService.getComById(task.getComId());
        byte[] fwFileBlock = getFirmwareBlock(fwFileId, blockSize, blockNumber);
        if(comConfigDMO.getProtocolId() == ProtocolType.ANSI_C12_18_Protocol.getProtocolId()) {
            AnsiSubDataItemDTO dataItemDTO = new AnsiSubDataItemDTO();
            dataItemDTO.setTableId(0x0857);
            dataItemDTO.setTableOffset(blockNumber * blockSize);
            dataItemDTO.setByteCount(fwFileBlock.length);
            dataItemDTO.setRoleId(2);
            List<DataItemDTO> dataItemList = CollUtil.newArrayList(dataItemDTO);
            byte[] packets = packetService.writeSimpleDataPacket(dataItemList, meter, fwFileBlock, null);
            connectorService.sendPacket(task, atomicTask, 2, task.getIsODR(), packets);
        } else {
            String interfaceType = comConfigDMO.getInterfaceType();
            Boolean isShortName = comConfigDMO.getIsShortName();
            MeterConfigDMO meterConfigDMO = null;
            if (task.getMeterId() != null) {
                meterConfigDMO = assetService.getMeterConfigById(task.getMeterId());
                interfaceType = meterConfigDMO.getInterfaceType();
                isShortName = meterConfigDMO.getIsShortName();
            }
            List<DataItemDTO> dataItemDTOList = dataItemFacade.getDataItemsByOperation(meter, terminal, dictionary, getParameterMap(task, atomicTask), comConfigDMO.getModelId());
            if (CollectionUtils.isEmpty(dataItemDTOList)) {
                throw new UDISTaskExecutorException(TaskExecuteErrorCode.DATAITEM_NOT_EXISTS.toString());
            }
            DeviceSAPandKeys deviceKeys = meter != null ? assetManagementRemoteService.getMeterCommunicationKey(meter.getMeterId(), dataItemDTOList.get(0).getRoleId()) :
                    assetManagementRemoteService.getTerminalCommunicationKey(terminal.getComId(), dataItemDTOList.get(0).getRoleId());
            byte[] packets = packetService.imageBlockTransferAction(dataItemDTOList.get(0), interfaceType, meterConfigDMO, isShortName, deviceKeys.getWPort().intValue(), fwFileBlock, blockNumber);
            connectorService.sendPacket(task, atomicTask, dataItemDTOList.get(0).getRoleId(), task.getIsODR(), packets);
        }
        AtomicTaskState returnState = createAtomicState(ImageBlockTransferHandler.class);
        returnState.setBlockNumber(blockNumber);
        if (state != null) {
            returnState.setStored(true);
        }
        return returnState;
    }

    private byte[] getFirmwareBlock(Long fwFileId, Integer blockSize, Integer blockNumber) {
        String fwCacheKey = RedisKeys.getTaskFirmwareFile(fwFileId, blockSize);
        String s = String.valueOf(redisUtils.hget(fwCacheKey, String.valueOf(blockNumber)));
        Set<String> keys = redisUtils.hKeys(fwCacheKey);
        Integer totalBlockNumber = keys.size();
        byte[] fileBytes = ByteUtils.hexStringToByte(s);
        log.debug("fwFileId={},blockNumber={}, totalBlockNumber={}, blockSize={}, length {}: {}", fwFileId, blockNumber, totalBlockNumber, blockSize, fileBytes.length, ByteUtils.byteToHexString(fileBytes));
        return fileBytes;
    }

    @Override
    public Boolean handleFailure(Task task, AtomicTask atomicTask, String message) {
        if (UDISTaskExecutorException.COM_STATUS_EXCEPTION.equalsIgnoreCase(message)) {
            log.warn("image transfer pause because of com offline");
            return false;
        }
        firmwareService.saveFwuReport(task, atomicTask, -1, TemplateFwUpgrade.TaskType.ImageTransfer, "fwu init execute failed", null);
        log.error("failExecuteImageTransfer:{}", message);
        return true;
    }
}
